package com.qcrud.mapper;

import com.qcrud.Qcrud;
import com.qcrud.annotation.*;
import com.qcrud.core.SqlCommandType;
import com.qcrud.core.SqlData;
import com.qcrud.core.SqlManager;
import com.qcrud.core.SqlMeta;
import com.qcrud.core.paging.Paging;
import com.qcrud.exception.QcrudException;
import com.qcrud.tookit.ClassUtils;
import com.qcrud.tookit.ConstantPool;
import com.qcrud.tookit.SqlParamUtils;
import lombok.extern.slf4j.Slf4j;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public record MapperMethod(Qcrud qcrud, Class mapperInterface, Method method) {
    private static final Map<String, Class> ENTITY_CLASS = new ConcurrentHashMap<>();

    public Object execute(Object[] args) {
        String methodName = method.getName();
        log.debug("execute mapper method: " + methodName);
        String declaringClassName = method.getDeclaringClass().getName();
        String statementKey = declaringClassName + "." + methodName;
        SqlMeta sqlMeta = SqlManager.get(statementKey);
        if (null == sqlMeta) {
            Annotation[] annotations = method.getAnnotations();
            for (Annotation annotation : annotations) {
                if (annotation instanceof Insert insert) {
                    sqlMeta = new SqlMeta(SqlCommandType.INSERT, insert.value(), method.getGenericReturnType());
                } else if (annotation instanceof Delete sqlDelete) {
                    sqlMeta = new SqlMeta(SqlCommandType.DELETE, sqlDelete.value(), method.getGenericReturnType());
                } else if (annotation instanceof Update sqlUpdate) {
                    sqlMeta = new SqlMeta(SqlCommandType.UPDATE, sqlUpdate.value(), method.getGenericReturnType());
                } else if (annotation instanceof Select select) {
                    sqlMeta = new SqlMeta(SqlCommandType.SELECT, select.value(), method.getGenericReturnType());
                } else if (annotation instanceof SqlCall sqlCall) {
                    sqlMeta = new SqlMeta(SqlCommandType.SELECT, sqlCall.value(), method.getGenericReturnType());
                }
            }
            if (null == sqlMeta) {
                throw new QcrudException("Cannot execute method，statementKey: " + statementKey);
            }
            SqlManager.put(statementKey, sqlMeta);
        }

        // md sql 补充返回类型
        if (null == sqlMeta.getReturnType()) {
            sqlMeta.setReturnType(method.getGenericReturnType());
        }

        // 执行 SQL 数据信息
        SqlData sqlData = new SqlData(sqlMeta, statementKey, ENTITY_CLASS.computeIfAbsent(declaringClassName, k -> {
            ParameterizedType parameterizedType = (ParameterizedType) mapperInterface.getGenericInterfaces()[0];
            Type beanClazz = parameterizedType.getActualTypeArguments()[0];
            return ClassUtils.getClass(beanClazz);
        }));
        int argSize = null == args ? 0 : args.length;
        if (argSize > 0) {
            int i = 0;
            List<Param> paramList = SqlParamUtils.getSqlParams(method);
            if (argSize != paramList.size()) {
                if (args[0] instanceof Paging) {
                    // 索引跳过分页参数
                    i++;
                } else {
                    // 参数不匹配且第一位非分页参数对象抛出异常
                    throw new QcrudException("execute mapper method: " + methodName + ", each parameter must be annotated with @SqlParam ");
                }
            }
            Map<String, Object> params = new HashMap<>();
            if (i > 0) {
                params.put(ConstantPool.PAGING_KEY, args[0]);
            }
            for (Param param : paramList) {
                params.put(param.value(), args[i++]);
            }
            // 创建一个空 SQl 参数列表
            params.put(ConstantPool.SQL_PARAM_KEY, new ArrayList<>());
            sqlData.setParams(params);
        }
        return qcrud.execute(sqlData);
    }
}

